LEÇON 4

Programmation Orientée Objet en Python

Classes, Objets, Héritage et Polymorphisme

Français - POO avec Python

La POO (Programmation Orientée Objet) est un paradigme de programmation qui organise le code autour d'"objets" qui contiennent à la fois des données (attributs) et des comportements (méthodes).

1. Classes et Objets

Une classe est un modèle pour créer des objets. Un objet est une instance d'une classe.

# Définition d'une classe simple
class Personne:
    # Constructeur (méthode spéciale __init__)
    def __init__(self, nom, age):
        # Attributs d'instance
        self.nom = nom
        self.age = age
        self.ville = "Paris" # Valeur par défaut

    # Méthode d'instance
    def se_presenter(self):
        return f"Je m'appelle {self.nom} et j'ai {self.age} ans."

    # Méthode pour modifier un attribut
    def anniversaire(self):
        self.age += 1
        return f"Joyeux anniversaire! J'ai maintenant {self.age} ans."

# Création d'objets (instances)
personne1 = Personne("Alice", 25)
personne2 = Personne("Bob", 30)

# Utilisation des méthodes
print(personne1.se_presenter())
print(personne2.se_presenter())
print(personne1.anniversaire())
Le mot-clé self fait référence à l'instance actuelle de la classe. Il est obligatoire comme premier paramètre des méthodes d'instance.

2. Attributs de Classe et Méthodes Statiques

# Attributs de classe (partagés par toutes les instances)
class Employe:
    # Attribut de classe
    entreprise = "TechCorp"
    nombre_employes = 0 # Compteur partagé

    def __init__(self, nom, salaire):
        self.nom = nom
        self.salaire = salaire
        # Incrémenter le compteur d'employés
        Employe.nombre_employes += 1

    # Méthode statique (n'utilise pas self)
    @staticmethod
    def info_entreprise():
        return f"{Employe.entreprise} - Employés: {Employe.nombre_employes}"

    # Méthode de classe (utilise cls au lieu de self)
    @classmethod
    def creer_employe_par_defaut(cls):
        return cls("Nouvel Employé", 30000)

# Utilisation
emp1 = Employe("Alice", 50000)
emp2 = Employe("Bob", 45000)

print(Employe.info_entreprise()) # Méthode statique
print("Entreprise:", emp1.entreprise) # Attribut de classe

emp3 = Employe.creer_employe_par_defaut()
print(emp3.nom, emp3.salaire)
Différence importante :
@staticmethod : Méthode utilitaire qui n'a pas besoin d'accéder aux attributs
@classmethod : Méthode qui travaille avec la classe plutôt qu'une instance

3. Héritage

L'héritage permet de créer une nouvelle classe basée sur une classe existante.

# Classe parent (superclasse)
class Animal:
    def __init__(self, nom, age):
        self.nom = nom
        self.age = age

    def parler(self):
        return "Je fais un son"

    def se_decrire(self):
        return f"Je suis {self.nom}, j'ai {self.age} ans."

# Classes enfants (sous-classes)
class Chien(Animal):
    def __init__(self, nom, age, race):
        # Appel du constructeur parent
        super().__init__(nom, age)
        self.race = race

    def parler(self):
        return "Woof! Woof!"

class Chat(Animal):
    def parler(self):
        return "Miaou!"

# Utilisation
mon_chien = Chien("Rex", 3, "Labrador")
mon_chat = Chat("Misty", 2)

print(mon_chien.se_decrire())
print(mon_chien.parler())
print(mon_chat.parler())
print("Race du chien:", mon_chien.race)

4. Encapsulation et Propriétés

L'encapsulation permet de protéger les données internes d'une classe.

class CompteBancaire:
    def __init__(self, titulaire, solde_initial):
        self.titulaire = titulaire
        # Attribut privé (convention: commence par _)
        self._solde = solde_initial

    # Getter (accès contrôlé)
    @property
    def solde(self):
        return self._solde

    # Méthode pour déposer de l'argent
    def deposer(self, montant):
        if montant > 0:
            self._solde += montant
            return True
        return False

    # Méthode pour retirer de l'argent
    def retirer(self, montant):
        if 0 < montant <= self._solde:
            self._solde -= montant
            return True
        return False

# Utilisation
compte = CompteBancaire("Alice", 1000)
print("Solde initial:", compte.solde) # Utilisation du getter

compte.deposer(500)
print("Après dépôt:", compte.solde)

if compte.retirer(200):
    print("Retrait réussi. Nouveau solde:", compte.solde)
else:
    print("Retrait impossible")

# compte.solde = 5000 # Erreur! Pas de setter défini

English - Object-Oriented Programming

OOP (Object-Oriented Programming) is a programming paradigm that organizes code around "objects" that contain both data (attributes) and behaviors (methods).

1. Classes and Objects

A class is a blueprint for creating objects. An object is an instance of a class.

# Definition of a simple class
class Person:
    # Constructor (special __init__ method)
    def __init__(self, name, age):
        # Instance attributes
        self.name = name
        self.age = age
        self.city = "Paris" # Default value

    # Instance method
    def introduce(self):
        return f"My name is {self.name} and I'm {self.age} years old."

    # Method to modify an attribute
    def birthday(self):
        self.age += 1
        return f"Happy birthday! I'm now {self.age} years old."

# Creating objects (instances)
person1 = Person("Alice", 25)
person2 = Person("Bob", 30)

# Using methods
print(person1.introduce())
print(person2.introduce())
print(person1.birthday())
The self keyword refers to the current instance of the class. It's required as the first parameter of instance methods.

2. Class Attributes and Static Methods

# Class attributes (shared by all instances)
class Employee:
    # Class attribute
    company = "TechCorp"
    employee_count = 0 # Shared counter

    def __init__(self, name, salary):
        self.name = name
        self.salary = salary
        # Increment employee counter
        Employee.employee_count += 1

    # Static method (doesn't use self)
    @staticmethod
    def company_info():
        return f"{Employee.company} - Employees: {Employee.employee_count}"

    # Class method (uses cls instead of self)
    @classmethod
    def create_default_employee(cls):
        return cls("New Employee", 30000)

# Usage
emp1 = Employee("Alice", 50000)
emp2 = Employee("Bob", 45000)

print(Employee.company_info()) # Static method
print("Company:", emp1.company) # Class attribute

emp3 = Employee.create_default_employee()
print(emp3.name, emp3.salary)
Important difference:
@staticmethod: Utility method that doesn't need to access attributes
@classmethod: Method that works with the class rather than an instance

3. Inheritance

Inheritance allows creating a new class based on an existing class.

# Parent class (superclass)
class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def speak(self):
        return "I make a sound"

    def describe(self):
        return f"I am {self.name}, I'm {self.age} years old."

# Child classes (subclasses)
class Dog(Animal):
    def __init__(self, name, age, breed):
        # Call parent constructor
        super().__init__(name, age)
        self.breed = breed

    def speak(self):
        return "Woof! Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

# Usage
my_dog = Dog("Rex", 3, "Labrador")
my_cat = Cat("Misty", 2)

print(my_dog.describe())
print(my_dog.speak())
print(my_cat.speak())
print("Dog breed:", my_dog.breed)

4. Encapsulation and Properties

Encapsulation allows protecting a class's internal data.

class BankAccount:
    def __init__(self, holder, initial_balance):
        self.holder = holder
        # Private attribute (convention: starts with _)
        self._balance = initial_balance

    # Getter (controlled access)
    @property
    def balance(self):
        return self._balance

    # Method to deposit money
    def deposit(self, amount):
        if amount > 0:
            self._balance += amount
            return True
        return False

    # Method to withdraw money
    def withdraw(self, amount):
        if 0 < amount <= self._balance:
            self._balance -= amount
            return True
        return False

# Usage
account = BankAccount("Alice", 1000)
print("Initial balance:", account.balance) # Using getter

account.deposit(500)
print("After deposit:", account.balance)

if account.withdraw(200):
    print("Withdrawal successful. New balance:", account.balance)
else:
    print("Withdrawal failed")

# account.balance = 5000 # Error! No setter defined